
// <copyright file="DbConnectionFactory.cs" company="Subhadra Software Solutions">
//   
// </copyright>
// <summary>
//   This factory class serves Db connections. This is a Singleton class.
// </summary>

namespace SubhadraSolutions.Sharp.Utils.Data
{
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Data;
    using System.Data.SqlClient;

    using SubhadraSolutions.Sharp.Utils.Reflection;
    using SubhadraSolutions.Sharp.Utils.Validation;

    /// <summary>
    ///     This factory class serves Db connections. This is a Singleton class.
    /// </summary>
    public sealed class DbConnectionFactory : AbstractInitializable
    {
        #region Static Fields

        /// <summary>
        ///     The instance.
        /// </summary>
        private static readonly DbConnectionFactory InstanceObject = new DbConnectionFactory();

        #endregion Static Fields

        #region Fields

        /// <summary>
        /// The connection strings.
        /// </summary>
        private readonly Dictionary<string, string> _connectionStrings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

        /// <summary>
        ///     The _sync lock.
        /// </summary>
        private readonly object _syncLock = new object();

        /// <summary>
        ///     The _connection class type.
        /// </summary>
        private Type _connectionClassType;

        /// <summary>
        ///     The _connection string.
        /// </summary>
        private string _connectionString;

        #endregion Fields

        #region Constructors and Finalizers

        /// <summary>
        ///     Prevents a default instance of the <see cref="DbConnectionFactory" /> class from being created.
        /// </summary>
        private DbConnectionFactory()
        {
        }

        #endregion Constructors and Finalizers

        #region Public Properties

        /// <summary>
        ///     Gets the instance.
        /// </summary>
        /// <value>The instance.</value>
        public static DbConnectionFactory Instance
        {
            get
            {
                return InstanceObject;
            }
        }

        /// <summary>
        ///     Gets or sets the type of the connection class.
        /// </summary>
        /// <value>The type of the connection class.</value>
        public Type ConnectionClassType
        {
            get
            {
                return this._connectionClassType;
            }

            set
            {
                ValidateConnectionClassType(value);
                this._connectionClassType = value;
            }
        }

        /// <summary>
        ///     Gets or sets the connection string.
        /// </summary>
        /// <value>The connection string.</value>
        public string ConnectionString
        {
            get
            {
                return this._connectionString;
            }

            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    throw new ArgumentException(@"The value provided for ConnectionString is null or white space", "value");
                }

                this._connectionString = value;
            }
        }

        #endregion Public Properties

        #region Public Methods and Operators

        /// <summary>
        /// Creates the connection.
        /// </summary>
        /// <param name="connectionString">
        /// The connection String.
        /// </param>
        /// <returns>
        /// IDbConnection.
        /// </returns>
        public IDbConnection CreateConnection(string connectionString = null)
        {
            this.Initialize();
            IDbConnection connection = this.CreateConnectionPrivate(this.ConnectionClassType, connectionString);
            return connection;
        }

        /// <summary>
        /// The create connection.
        /// </summary>
        /// <param name="connectionClassType">
        /// The connection class type.
        /// </param>
        /// <param name="connectionString">
        /// The connection string.
        /// </param>
        /// <returns>
        /// The <see cref="IDbConnection"/>.
        /// </returns>
        public IDbConnection CreateConnection(Type connectionClassType, string connectionString = null)
        {
            this.Initialize();
            return this.CreateConnectionPrivate(connectionClassType, connectionString);
        }

        /// <summary>
        /// The create connection.
        /// </summary>
        /// <param name="connectionString">
        /// The connection string.
        /// </param>
        /// <typeparam name="T">
        /// </typeparam>
        /// <returns>
        /// The <see cref="IDbConnection"/>.
        /// </returns>
        public IDbConnection CreateConnection<T>(string connectionString) where T : IDbConnection, new()
        {
            IDbConnection connection = this.CreateConnection(typeof(T), connectionString);
            return connection;
        }

        #endregion Public Methods and Operators

        #region Methods

        /// <summary>
        /// The initialize protected.
        /// </summary>
        /// <exception cref="System.Exception">No default connection string is specified in the configuration.</exception>
        /// <exception cref="Exception"></exception>
        protected override void InitializeProtected()
        {
            this.populateConnectionStrings();
            if (this._connectionClassType == null)
            {
                string connectionType = ConfigurationManager.AppSettings["ConnectionClassType"];
                if (!string.IsNullOrEmpty(connectionType))
                {
                    this.ConnectionClassType = ReflectionHelper.GetType(connectionType);
                }
                else
                {
                    this._connectionClassType = typeof(SqlConnection);
                }
            }

            if (this._connectionString == null)
            {
                this._connectionString = ConfigurationManager.AppSettings["DefaultConnectionString"];

                if (string.IsNullOrWhiteSpace(this._connectionString))
                {
                    throw new Exception("No default connection string is specified in the configuration.");
                }

                this._connectionString = this.getConnectionString(this._connectionString);
            }
        }

        /// <summary>
        /// The validate connection class type.
        /// </summary>
        /// <param name="connectionClassType">
        /// The connection class type.
        /// </param>
        /// <exception cref="System.ArgumentException">
        /// @The 'Type' provided for connectionClassType cannot be treated as IDbConnection;connectionClassType
        /// or
        /// @The 'Type' provided for connectionClassType should be a non-static, non-abstract class type;connectionClassType
        /// or
        /// @The 'Type' provided for connectionClassType must have a default constructor;connectionClassType
        /// </exception>
        /// <exception cref="ArgumentException">
        /// </exception>
        private static void ValidateConnectionClassType(Type connectionClassType)
        {
            Guard.ArgumentNotNull(connectionClassType, "connectionClassType");
            if (!typeof(IDbConnection).IsAssignableFrom(connectionClassType))
            {
                throw new ArgumentException(@"The 'Type' provided for connectionClassType cannot be treated as IDbConnection", "connectionClassType");
            }

            if (connectionClassType.IsAbstract || connectionClassType.IsInterface)
            {
                throw new ArgumentException(@"The 'Type' provided for connectionClassType should be a non-static, non-abstract class type", "connectionClassType");
            }

            if (connectionClassType.GetConstructor(Type.EmptyTypes) == null)
            {
                throw new ArgumentException(@"The 'Type' provided for connectionClassType must have a default constructor", "connectionClassType");
            }
        }

        /// <summary>
        /// The create connection.
        /// </summary>
        /// <param name="connectionClassType">
        /// The connection class type.
        /// </param>
        /// <param name="connectionString">
        /// The connection string.
        /// </param>
        /// <returns>
        /// The <see cref="IDbConnection"/>.
        /// </returns>
        private IDbConnection CreateConnectionPrivate(Type connectionClassType, string connectionString)
        {
            connectionString = this.getConnectionString(connectionString);
            ValidateConnectionClassType(connectionClassType);
            var connection = (IDbConnection)Activator.CreateInstance(connectionClassType);
            connection.ConnectionString = connectionString;
            connection.Open();
            return connection;
        }

        /// <summary>
        /// The get connection string.
        /// </summary>
        /// <param name="connectionString">
        /// The connection string.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        private string getConnectionString(string connectionString)
        {
            if (connectionString == null)
            {
                return this.getConnectionString(this._connectionString);
            }

            string cs;
            if (this._connectionStrings.TryGetValue(connectionString, out cs))
            {
                return cs;
            }

            return connectionString;
        }

        /// <summary>
        /// The populate connection strings.
        /// </summary>
        private void populateConnectionStrings()
        {
            this._connectionStrings.Clear();
            foreach (ConnectionStringSettings settings in ConfigurationManager.ConnectionStrings)
            {
                this._connectionStrings.Add(settings.Name, settings.ConnectionString);
            }
        }

        #endregion Methods
    }
}